package com.stary.pay.unionpay.api.util;

import java.io.UnsupportedEncodingException;
import java.security.cert.X509Certificate;

import com.stary.pay.unionpay.api.UnionpayConstants;

/**
 * <p>签名工具类</p>
 * @author stary {@link stary1993@qq.com}
 * @since 2019-6-20
 */
public class UnionpaySignature {


	/**
	 * 根据signMethod的值，提供三种计算签名的方法
	 * @param data  待签名数据Map键值对形式
	 * @param secureKey 
	 * @param signCertPwd 
	 * @param encoding 编码
	 * @return 签名是否成功
	 */
	public static boolean sign(UnionpayHashMap data,String secureKey, String signCertPwd, String encoding) {		
		if (StringUtils.isEmpty(encoding)) {
			encoding = UnionpayConstants.CHARSET_UTF8;
		}
		String signMethod = data.get(UnionpayConstants.SIGNMETHOD);
		String version = data.get(UnionpayConstants.VERSION);
		if (!UnionpayConstants.VERSION_1_0_0.equals(version) 
				&& !UnionpayConstants.VERSION_5_0_1.equals(version) 
				&& StringUtils.isEmpty(signMethod)) {
			UnionpayUtils.getLogger().error("signMethod must not null.");
			return false;
		}
		if (StringUtils.isEmpty(version)) {
			UnionpayUtils.getLogger().error("version must not null.");
			return false;
		}
		if (UnionpayConstants.SIGNMETHOD_RSA.equals(signMethod) || UnionpayConstants.VERSION_1_0_0.equals(version) || UnionpayConstants.VERSION_5_0_1.equals(version)) {
			if (UnionpayConstants.VERSION_5_0_0.equals(version) || UnionpayConstants.VERSION_1_0_0.equals(version) || UnionpayConstants.VERSION_5_0_1.equals(version)) {
				// 设置签名证书序列号
				data.put(UnionpayConstants.CERTID, UnionpayCertHolder.getSignCertId());
				// 将Map信息转换成key1=value1&key2=value2的形式
				String stringData = UnionpayUtils.coverMapToString(data);
				UnionpayUtils.getLogger().debug("print a sequence of request messages to be signed:[" + stringData + "]");
				byte[] byteSign = null;
				String stringSign = null;
				try {
					// 通过SHA1进行摘要并转16进制
					byte[] signDigest = UnionpayEncrypt.sha1X16(stringData, encoding);
					UnionpayUtils.getLogger().debug("print the:[" + new String(signDigest)+ "]");
					byteSign = UnionpayEncrypt.base64Encode(UnionpayEncrypt.signBySoft(
							UnionpayCertHolder.getSignCertPrivateKey(signCertPwd), signDigest));
					stringSign = new String(byteSign);
					// 设置签名域值
					data.put(UnionpayConstants.SIGNATURE, stringSign);
					return true;
				} catch (Exception e) {
					UnionpayUtils.getLogger().error("sign error", e);
					return false;
				}
			} else if (UnionpayConstants.VERSION_5_1_0.equals(version)) {
				// 设置签名证书序列号
				data.put(UnionpayConstants.CERTID, UnionpayCertHolder.getSignCertId());
				// 将Map信息转换成key1=value1&key2=value2的形式
				String stringData = UnionpayUtils.coverMapToString(data);
				UnionpayUtils.getLogger().debug("print a sequence of request messages to be signed:[" + stringData + "]");
				byte[] byteSign = null;
				String stringSign = null;
				try {
					// 通过SHA256进行摘要并转16进制
					byte[] signDigest = UnionpayEncrypt.sha256X16(stringData, encoding);
					UnionpayUtils.getLogger().debug("print the:[" + new String(signDigest)+ "]");
					byteSign = UnionpayEncrypt.base64Encode(
							UnionpayEncrypt.signBySoft256(UnionpayCertHolder.getSignCertPrivateKey(signCertPwd), signDigest));
					stringSign = new String(byteSign);
					// 设置签名域值
					data.put(UnionpayConstants.SIGNATURE, stringSign);
					return true;
				} catch (Exception e) {
					UnionpayUtils.getLogger().error("sign error", e);
					return false;
				}
			}
		} else if (UnionpayConstants.SIGNMETHOD_SHA256.equals(signMethod)) {
			return signBySecureKey(data, secureKey, encoding);
		} else if (UnionpayConstants.SIGNMETHOD_SM3.equals(signMethod)) {
			return signBySecureKey(data, secureKey, encoding);
		}
		return false;
	}

	/**
	 * 通过传入的证书绝对路径和证书密码读取签名证书进行签名并返回签名值<br> 
	 * @param data 待签名数据Map键值对形式
	 * @param secureKey 安全秘钥
	 * @return 签名值
	 */
	public static boolean signBySecureKey(UnionpayHashMap data, String secureKey, String encoding) {		
		if (StringUtils.isEmpty(encoding)) {
			encoding = UnionpayConstants.CHARSET_UTF8;
		}
		if (StringUtils.isEmpty(secureKey)) {
			UnionpayUtils.getLogger().error("secureKey is empty");
			return false;
		}
		String signMethod = data.get(UnionpayConstants.SIGNMETHOD);
		if (StringUtils.isEmpty(signMethod)) {
			UnionpayUtils.getLogger().error("signMethod must not null");
			return false;
		}

		if (UnionpayConstants.SIGNMETHOD_SHA256.equals(signMethod)) {
			// 将Map信息转换成key1=value1&key2=value2的形式
			String stringData = UnionpayUtils.coverMapToString(data);
			UnionpayUtils.getLogger().debug("request message string to be signed:[" + stringData + "]");
			String strBeforeSha256 = stringData
					+ UnionpayConstants.AMPERSAND
					+ UnionpayEncrypt.sha256X16Str(secureKey, encoding);
			String strAfterSha256 = UnionpayEncrypt.sha256X16Str(strBeforeSha256, encoding);
			// 设置签名域值
			data.put(UnionpayConstants.SIGNATURE, strAfterSha256);
			return true;
		} else if (UnionpayConstants.SIGNMETHOD_SM3.equals(signMethod)) {
			String stringData = UnionpayUtils.coverMapToString(data);
			UnionpayUtils.getLogger().debug("request message string to be signed:[" + stringData + "]");
			String strBeforeSM3 = stringData
					+ UnionpayConstants.AMPERSAND
					+ UnionpayEncrypt.sm3X16Str(secureKey, encoding);
			String strAfterSM3 = UnionpayEncrypt.sm3X16Str(strBeforeSM3, encoding);
			// 设置签名域值
			data.put(UnionpayConstants.SIGNATURE, strAfterSM3);
			return true;
		}
		return false;
	}

	/**
	 * 通过传入的签名密钥进行签名并返回签名值
	 * @param data 待签名数据Map键值对形式
	 * @param encoding 编码
	 * @param certPath 证书绝对路径
	 * @param certPwd 证书密码
	 * @return 签名值
	 */
	public static boolean signByCertInfo(UnionpayHashMap data,
			String certPath, String certPwd, String encoding) {		
		if (StringUtils.isEmpty(encoding)) {
			encoding = UnionpayConstants.CHARSET_UTF8;
		}
		if (StringUtils.isEmpty(certPath) || StringUtils.isEmpty(certPwd)) {
			UnionpayUtils.getLogger().error("certPath or certPwd is empty");
			return false;
		}
		String signMethod = data.get(UnionpayConstants.SIGNMETHOD);
		String version = data.get(UnionpayConstants.VERSION);
		if (!UnionpayConstants.VERSION_1_0_0.equals(version) 
				&& !UnionpayConstants.VERSION_5_0_1.equals(version) 
				&& StringUtils.isEmpty(signMethod)) {
			UnionpayUtils.getLogger().error("signMethod must Not null");
			return false;
		}
		if (StringUtils.isEmpty(version)) {
			UnionpayUtils.getLogger().error("version must Not null");
			return false;
		}

		if (UnionpayConstants.SIGNMETHOD_RSA.equals(signMethod) || UnionpayConstants.VERSION_1_0_0.equals(version) || UnionpayConstants.VERSION_5_0_1.equals(version)) {
			if (UnionpayConstants.VERSION_5_0_0.equals(version) || UnionpayConstants.VERSION_1_0_0.equals(version) || UnionpayConstants.VERSION_5_0_1.equals(version)) {
				// 设置签名证书序列号
				data.put(UnionpayConstants.CERTID, UnionpayCertHolder.getCertIdByKeyStoreMap(certPath, certPwd));
				// 将Map信息转换成key1=value1&key2=value2的形式
				String stringData = UnionpayUtils.coverMapToString(data);
				UnionpayUtils.getLogger().debug("request message to be signed:[" + stringData + "]");
				byte[] byteSign = null;
				String stringSign = null;
				try {
					// 通过SHA1进行摘要并转16进制
					byte[] signDigest = UnionpayEncrypt.sha1X16(stringData, encoding);
					byteSign = UnionpayEncrypt.base64Encode(UnionpayEncrypt.signBySoft(
							UnionpayCertHolder.getSignCertPrivateKeyByStoreMap(certPath, certPwd), signDigest));
					stringSign = new String(byteSign);
					// 设置签名域值
					data.put(UnionpayConstants.SIGNATURE, stringSign);
					return true;
				} catch (Exception e) {
					UnionpayUtils.getLogger().error("sign error", e);
					return false;
				}
			} else if (UnionpayConstants.VERSION_5_1_0.equals(version)) {
				// 设置签名证书序列号
				data.put(UnionpayConstants.CERTID, UnionpayCertHolder.getCertIdByKeyStoreMap(certPath, certPwd));
				// 将Map信息转换成key1=value1&key2=value2的形式
				String stringData = UnionpayUtils.coverMapToString(data);
				UnionpayUtils.getLogger().debug("request message string to be signed:[" + stringData + "]");
				byte[] byteSign = null;
				String stringSign = null;
				try {
					// 通过SHA256进行摘要并转16进制
					byte[] signDigest = UnionpayEncrypt.sha256X16(stringData, encoding);
					byteSign = UnionpayEncrypt.base64Encode(UnionpayEncrypt.signBySoft256(
							UnionpayCertHolder.getSignCertPrivateKeyByStoreMap(certPath, certPwd), signDigest));
					stringSign = new String(byteSign);
					// 设置签名域值
					data.put(UnionpayConstants.SIGNATURE, stringSign);
					return true;
				} catch (Exception e) {
					UnionpayUtils.getLogger().error("sign error", e);
					return false;
				}
			}

		} 
		return false;
	}

	/**
	 * 验证签名
	 * @param resData 返回报文数据
	 * @param secureKey 安全秘钥
	 * @param encoding 编码格式
	 * @return
	 */
	public static boolean validateBySecureKey(UnionpayHashMap resData, String secureKey, String encoding) {
		UnionpayUtils.getLogger().debug("validate sign begin...");
		if (StringUtils.isEmpty(encoding)) {
			encoding = UnionpayConstants.CHARSET_UTF8;
		}
		String signMethod = resData.get(UnionpayConstants.SIGNMETHOD);
		if (UnionpayConstants.SIGNMETHOD_SHA256.equals(signMethod)) {
			// 1.进行SHA256验证
			String stringSign = resData.get(UnionpayConstants.SIGNATURE);
			UnionpayUtils.getLogger().debug("the original sign:[" + stringSign + "]");
			// 将Map信息转换成key1=value1&key2=value2的形式
			String stringData = UnionpayUtils.coverMapToString(resData);
			UnionpayUtils.getLogger().debug("check return message string:[" + stringData + "]");
			String strBeforeSha256 = stringData + UnionpayConstants.AMPERSAND
					+ UnionpayEncrypt.sha256X16Str(secureKey, encoding);
			String strAfterSha256 = UnionpayEncrypt.sha256X16Str(strBeforeSha256,
					encoding);
			return stringSign.equals(strAfterSha256);
		} else if (UnionpayConstants.SIGNMETHOD_SM3.equals(signMethod)) {
			// 1.进行SM3验证
			String stringSign = resData.get(UnionpayConstants.SIGNATURE);
			UnionpayUtils.getLogger().debug("the original sign:[" + stringSign + "]");
			// 将Map信息转换成key1=value1&key2=value2的形式
			String stringData = UnionpayUtils.coverMapToString(resData);
			UnionpayUtils.getLogger().debug("request message string to be signed:[" + stringData + "]");
			String strBeforeSM3 = stringData + UnionpayConstants.AMPERSAND
					+ UnionpayEncrypt.sm3X16Str(secureKey, encoding);
			String strAfterSM3 = UnionpayEncrypt.sm3X16Str(strBeforeSM3, encoding);
			return stringSign.equals(strAfterSM3);
		}
		return false;
	}

	/**
	 * 验证签名
	 * @param resData 返回报文数据
	 * @param secureKey 安全密钥
	 * @param validateCertDir
	 * @param rootCertFile
	 * @param middleCertFile
	 * @param isCheckCNName
	 * @param encoding 编码格式
	 * @return
	 */
	public static boolean validate(UnionpayHashMap resData, String secureKey, String validateCertDir, 
			String rootCertFile, String middleCertFile, boolean isCheckCNName, String encoding) {
		UnionpayUtils.getLogger().debug("validate sign begin...");
		if (StringUtils.isEmpty(encoding)) {
			encoding = UnionpayConstants.CHARSET_UTF8;
		}
		String signMethod = resData.get(UnionpayConstants.SIGNMETHOD);
		String version = resData.get(UnionpayConstants.VERSION);
		if (UnionpayConstants.SIGNMETHOD_RSA.equals(signMethod) || UnionpayConstants.VERSION_1_0_0.equals(version) || UnionpayConstants.VERSION_5_0_1.equals(version)) {
			// 获取返回报文的版本号
			if (UnionpayConstants.VERSION_5_0_0.equals(version) || UnionpayConstants.VERSION_1_0_0.equals(version) || UnionpayConstants.VERSION_5_0_1.equals(version)) {
				String stringSign = resData.get(UnionpayConstants.SIGNATURE);
				UnionpayUtils.getLogger().debug("the original sign:[" + stringSign + "]");
				// 从返回报文中获取certId ，然后去证书静态Map中查询对应验签证书对象
				String certId = resData.get(UnionpayConstants.CERTID);
				UnionpayUtils.getLogger().debug("The public key sequence number used for the return message string verifier:[" + certId + "]");
				// 将Map信息转换成key1=value1&key2=value2的形式
				String stringData = UnionpayUtils.coverMapToString(resData);
				UnionpayUtils.getLogger().debug("request message string to be signed:[" + stringData + "]");
				try {
					// 验证签名需要用银联发给商户的公钥证书.
					return UnionpayEncrypt.validateSignBySoft(
							UnionpayCertHolder.getValidatePublicKey(certId, signMethod, validateCertDir), 
							UnionpayEncrypt.base64Decode(stringSign.getBytes(encoding)),
							UnionpayEncrypt.sha1X16(stringData, encoding));
				} catch (UnsupportedEncodingException e) {
					UnionpayUtils.getLogger().error(e.getMessage(), e);
				} catch (Exception e) {
					UnionpayUtils.getLogger().error(e.getMessage(), e);
				}
			} else if (UnionpayConstants.VERSION_5_1_0.equals(version)) {
				// 1.从返回报文中获取公钥信息转换成公钥对象
				String strCert = resData.get(UnionpayConstants.SIGNPUBKEYCERT);
				X509Certificate x509Cert = UnionpayCertHolder.genCertificateByStr(strCert);
				if(x509Cert == null) {
					UnionpayUtils.getLogger().error("convert signPubKeyCert failed.");
					return false;
				}
				// 2.验证证书链
				if (!UnionpayCertHolder.verifyCertificate(x509Cert, rootCertFile, middleCertFile, isCheckCNName)) {
					UnionpayUtils.getLogger().error("Public key certificate verification failed, cert information:[" + strCert + "]");
					return false;
				}
				// 3.验签
				String stringSign = resData.get(UnionpayConstants.SIGNATURE);
				UnionpayUtils.getLogger().debug("the original sign:[" + stringSign + "]");
				// 将Map信息转换成key1=value1&key2=value2的形式
				String stringData = UnionpayUtils.coverMapToString(resData);
				UnionpayUtils.getLogger().debug("return message string to be verified:[" + stringData + "]");
				try {
					// 验证签名需要用银联发给商户的公钥证书.
					boolean result = UnionpayEncrypt.validateSignBySoft256(x509Cert.getPublicKey(), 
							UnionpayEncrypt.base64Decode(stringSign.getBytes(encoding)), 
							UnionpayEncrypt.sha256X16(stringData, encoding));
					UnionpayUtils.getLogger().debug("verify signature " + (result? "succeed":"failed"));
					return result;
				} catch (UnsupportedEncodingException e) {
					UnionpayUtils.getLogger().error(e.getMessage(), e);
				} catch (Exception e) {
					UnionpayUtils.getLogger().error(e.getMessage(), e);
				}
			}

		} else if (UnionpayConstants.SIGNMETHOD_SHA256.equals(signMethod)) {
			// 1.进行SHA256验证
			String stringSign = resData.get(UnionpayConstants.SIGNATURE);
			UnionpayUtils.getLogger().debug("the original sign:[" + stringSign + "]");
			// 将Map信息转换成key1=value1&key2=value2的形式
			String stringData = UnionpayUtils.coverMapToString(resData);
			UnionpayUtils.getLogger().debug("return message string to be verified:[" + stringData + "]");
			String strBeforeSha256 = stringData
					+ UnionpayConstants.AMPERSAND
					+ UnionpayEncrypt.sha256X16Str(secureKey, encoding);
			String strAfterSha256 = UnionpayEncrypt.sha256X16Str(strBeforeSha256,
					encoding);
			boolean result =  stringSign.equals(strAfterSha256);
			UnionpayUtils.getLogger().debug("verify signature " + (result? "succeed":"failed"));
			return result;
		} else if (UnionpayConstants.SIGNMETHOD_SM3.equals(signMethod)) {
			// 1.进行SM3验证
			String stringSign = resData.get(UnionpayConstants.SIGNATURE);
			UnionpayUtils.getLogger().debug("the original sign:[" + stringSign + "]");
			// 将Map信息转换成key1=value1&key2=value2的形式
			String stringData = UnionpayUtils.coverMapToString(resData);
			UnionpayUtils.getLogger().debug("return message string to be verified:[" + stringData + "]");
			String strBeforeSM3 = stringData + UnionpayConstants.AMPERSAND
					+ UnionpayEncrypt.sm3X16Str(secureKey, encoding);
			String strAfterSM3 = UnionpayEncrypt.sm3X16Str(strBeforeSM3, encoding);
			boolean result =  stringSign.equals(strAfterSM3);
			UnionpayUtils.getLogger().debug("verify signature " + (result ? "succeed" : "failed"));
			return result;
		}
		return false;
	}

	/**
	 * 获取应答报文中的加密公钥证书,并存储到本地,并备份原始证书<br/>
	 * 更新成功则返回1，无更新返回0，失败异常返回-1。
	 * @param resData
	 * @param encryptCertFile 本地证书
	 * @param encoding 
	 * @return
	 */
	public static int getEncryptCert(UnionpayHashMap resData, String encryptCertFile, String encoding) {
		String strCert = resData.get(UnionpayConstants.ENCRYPTPUBKEYCERT);
		String certType = resData.get(UnionpayConstants.CERTTYPE);
		if (StringUtils.isEmpty(strCert) || StringUtils.isEmpty(certType)) {
			return -1;
		}
		X509Certificate x509Cert = UnionpayCertHolder.genCertificateByStr(strCert);
		if (UnionpayConstants.CERT_TYPE_01.equals(certType)) {
			// 更新敏感信息加密公钥
			if (!UnionpayCertHolder.getEncryptCertId(encryptCertFile).equals(x509Cert.getSerialNumber().toString())) {
				// ID不同时进行本地证书更新操作
				String newLocalCertFile = UnionpayUtils.genBackupName(encryptCertFile);
				// 1.将本地证书进行备份存储
				if (!UnionpayUtils.copyFile(encryptCertFile, newLocalCertFile)) {
					UnionpayUtils.getLogger().warn("encryptCertPublicKey update failed");
					return -1;
				}
				// 2.备份成功,进行新证书的存储
				if (!UnionpayUtils.writeFile(newLocalCertFile, strCert, encoding)) {
					UnionpayUtils.getLogger().warn("encryptCertPublicKey update failed");
					return -1;
				}
				UnionpayUtils.getLogger().debug("save new encryptPubKeyCert succeed");
				UnionpayCertHolder.resetEncryptCertPublicKey();
				return 1;
			}else {
				UnionpayUtils.getLogger().warn("encryptCertPublicKey not update");
				return 0;
			}

		} else if (UnionpayConstants.CERT_TYPE_02.equals(certType)) {
			//			// 更新磁道加密公钥
			//			if (!UnionpayCertHolder.getEncryptTrackCertId(encryptCertFile).equals(
			//					x509Cert.getSerialNumber().toString())) {
			//				// ID不同时进行本地证书更新操作
			//				String newLocalCertFile = UnionpayUtils.genBackupName(localCertFile);
			//				// 1.将本地证书进行备份存储
			//				if (!UnionpayUtils.copyFile(localCertFile, newLocalCertFile))
			//					return -1;
			//				// 2.备份成功,进行新证书的存储
			//				if (!UnionpayUtils.writeFile(newLocalCertFile, strCert, encoding))
			//					return -1;
			//				UnionpayUtils.getLogger().debug("save new encryptPubKeyCert success.");
			//				UnionpayCertHolder.resetEncryptTrackCertPublicKey();
			//				return 1;
			//			}else {
			return 0;
			//			}
		} else {
			UnionpayUtils.getLogger().warn("unknown certType:" + certType);
			return -1;
		}
	}

}
